/******************************************************************************
 * This file is part of ZEMB.
 *
 * ZEMB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ZEMB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ZEMB.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Project: zemb
 * Author : FergusZeng
 * Email  : cblock@126.com
 * git	  : https://gitee.com/newgolo/embedme.git
 * Copyright 2014-2020 @ ShenZhen ,China
*******************************************************************************/
#include "zemb/Tracer.h"
#include "zemb/ProcUtil.h"
#include "zemb/StrUtil.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <iostream>

#define  $TIMEOUT_EXECUTE    (3)    /* 默认超时时间为2秒 */
extern char** environ;              /* 当前环境变量(Linux全局变量) */

namespace zemb{
int ProcUtil::runCommand(std::string cmd, std::string& result, int timeoutSec)
{
	FILE* fp = popen(CSTR(cmd),"r");
	if (NULL==fp)
	{
		TRACE_ERR("ProcUtil::execute(%s), popen failed.",CSTR(cmd));
		return RC_ERROR;
	}
	std::string procName = StrUtil::trimHeadUnvisible(cmd);
    result="";
    if (timeoutSec<0) 
    {
    	procName = "";
    }
	timeoutSec = MAX(timeoutSec,$TIMEOUT_EXECUTE);
    int tout = timeoutSec;
    time_t startTime = ::time(NULL);
    while (1)
    {
        time_t endTime = ::time(NULL);
        if ((endTime-startTime)<0 || 
            (endTime-startTime)>=timeoutSec) 
        {
            break;
        }
        if (tout <= 0)
        {
            break;
        }
        int fd = fileno(fp);
        fd_set readfds;
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);
        struct timeval tv;
        tv.tv_sec = 1;
        tv.tv_usec= 0;
        int rc = select(fd + 1, &readfds, NULL, NULL, &tv);
        if (rc<=0 || (!FD_ISSET(fd, &readfds)))
        {
            tout -= 1;
            continue;
        }
        else
        {
            char buf[256]={0};
            rc = fread(buf, 1, sizeof(buf), fp);
            if(rc<=0)
            {
                break;
            }
            result += std::string(buf,rc);
        }  
    }
	if (!procName.empty())
	{
		stopProc(procName);
	}
	pclose(fp);
	return RC_OK;
}

std::string ProcUtil::runCommand(std::string cmd, int timeoutSec)
{	
    std::string result="";
	if (RC_OK==runCommand(cmd, result ,timeoutSec))
	{
		return result;
	}
	return result;
}

int ProcUtil::startProc(const std::string& procName, char** argv, int delays)
{
	int pid = fork();
	if (pid<0)
	{
		TRACE_ERR("ProcUtil::createProc, fork error: %s!",ERRSTR);
		return RC_ERROR;
	}
	else if(pid==0)
	{
		if (delays>0)
		{
			sleep(delays);
		}
		execvpe(CSTR(procName),argv,environ);
		exit(errno);
	}
	return pid;
}

void ProcUtil::stopProc(std::string procName)
{
	std::vector<int> pids = getPidsByName(CSTR(procName));
	for(auto i=0; i<pids.size(); i++)
	{
		kill(pids[i],SIGKILL);
	}
}
std::vector<int> ProcUtil::getPidsByName(const std::string& processName)
{
    std::vector<int> pidList;
    Directory dir;
    dir.enterDir("/proc");
    std::vector<std::string> fileNameList = dir.getFileList();
    int num = fileNameList.size();
    for(auto i=0; i<num; i++) 
    {
        if (!StrUtil::isIntString(fileNameList[i]))
        {
            continue;
        }
        File file;
        std::string statusFile = "/proc/"+fileNameList[i]+"/status";
        if(!file.open(CSTR(statusFile), IO_MODE_RD_ONLY))
        {
            continue;
        }
        std::string firstLine;
        if(RC_ERROR==file.readLine(firstLine))
        {
            continue;
        }
        std::vector<std::string> result=StrUtil::splitString(firstLine,":");
        if (result.size()!=2) 
        {
            continue;
        }
        std::string pid = StrUtil::trimTailBlank(result[1]);
        if (pid==processName) 
        {
            pidList.push_back(atoi(CSTR(fileNameList[i])));
        }
    }
    return pidList;
}

std::string ProcUtil::getEnv(const std::string& name)
{
	char* value = getenv(CSTR(name));
	if (value)
	{
		return std::string(value);
	}
	return "";
}

void ProcUtil::setEnv(const std::string& name, const std::string& value)
{
    setenv(CSTR(name),CSTR(value),1);
}

bool ProcUtil::setAffinity(int cpuMask)
{
    //使用命令查看进程xxx所运行的CPU: ps -eo pid,args,psr | grep xxx 
    cpu_set_t set;
    CPU_ZERO(&set); 
    for (int cpu=0; cpuMask>0; cpu++,cpuMask >>= 1)
    {
        if (cpuMask & 1)
        {
            CPU_SET(cpu, &set);
        }
    }
    if (sched_setaffinity(0, sizeof(set), &set) < 0) 
    {
        TRACE_ERR("Set CPU affinity error:%s",ERRSTR);
        return false; 
    } 
    return true;
}

ProcFifo::ProcFifo()
{
}

ProcFifo::~ProcFifo()
{
}

bool ProcFifo::open(const char* devName,int ioMode)
{
    if (devName==NULL)
    {
        TRACE_ERR_CLASS("fifo name is NULL.");
        return false;
    }
    if(!File::exists(devName))
    {
        int rc=mkfifo(devName,S_IFIFO|0666);
        if (rc<0) 
        {
            TRACE_ERR_CLASS("mkfifo error:%s.", ERRSTR); 
            return false;
        }
    }

	if (m_fd >= 0)
	{
		TRACE_ERR_CLASS("Device is already opened!");
		return false;
	}

	switch(ioMode)
	{
		case IO_MODE_RD_ONLY:
			m_fd = ::open(devName, O_RDONLY | O_NONBLOCK);
			break;
		case IO_MODE_WR_ONLY:
			m_fd = ::open(devName, O_WRONLY | O_NONBLOCK);
			break;
		case IO_MODE_RDWR_ONLY:
			m_fd = ::open(devName, O_RDWR | O_NONBLOCK);
			break;
		default:
			TRACE_ERR_CLASS("Unsupport IO Mode: %d",ioMode);
			return false;
	}

	if (m_fd<0)
	{
		TRACE_ERR_CLASS("Open %s error: %s",devName,ERRSTR);
		return false;
	}
	m_devName = devName;
	m_openMode = ioMode;
	return true;
}
}